<!DOCTYPE html>
<html lang="zh-CN">

<!-- Head tag -->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
  <meta name="baidu-site-verification" content="undefined" />
  <meta name="google-site-verification" content="undefined" />
  <meta name="msvalidate.01" content="undefined" />
  <meta name="360-site-verification" content="undefined" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- if page has tags, then add tags to keyword -->
  
  
  <meta name="keywords" content=",MySQL,">
  <!-- page.description has higher priority -->
  <meta name="description" content="高性能MySQL读后感，索引优化部分">
  <link rel="shortcut icon" href="/blog/img/logo.webp">
  <title>
    
    《高性能MySQL》笔记-索引优化 | Evrn
    
  </title>

  <link rel="canonical" href="/blog/post/mysql-index-use.html">
  
<link rel="stylesheet" href="/blog/css/reset.css">

  
<link rel="stylesheet" href="/blog/css/layout.css">

  
<link rel="stylesheet" href="/blog/css/markdown.css">

  <!-- Pygments Highlight CSS -->
  
<link rel="stylesheet" href="/blog/css/highlight.css">

  
<link rel="stylesheet" href="/blog/css/geektutu.css">

  <!-- global function -->
  <script>
    window.globalAddScript = function (url, onload, onerror) {
      var s = document.createElement('script');
      s.src = url;
      onload && (s.onload = onload);
      onerror && (s.onerror = onerror);
      document.body.appendChild(s);
    }
    window.globalAddCss = function (url) {
      var s = document.createElement('link');
      s.rel = 'stylesheet';
      s.href = url;
      document.body.appendChild(s);
    }
    window.getPosition = function (ele) {
      var x = 0, y = 0;
      while (ele) {
        x += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
        y += (ele.offsetTop - ele.scrollTop + ele.clientTop);
        ele = ele.offsetParent;
      }
      return { x: x, y: y };
    }
    window.getDom = function (str) { return document.querySelector(str) }
  </script>
  <!-- google ad -->
  
<meta name="generator" content="Hexo 4.2.0"></head>

<body>
    <header class="gkt-header col-xs-12 padding-0">
    <div id="gkt-nav" class="gkt-header-container">
        <a href="/blog/" class="gkt-header-title float-left">
            <img class="float-left" src="/blog/img/logo.webp" alt="">
            <span>Evrn</span>
        </a>
        <nav class="gkt-header-nav text-right">
            <ul>
                <li><a class="hidden-xs" href="/blog/">主页</a></li>
                <li><a href="/blog/series/">专题</a></li>
                <li><a href="/blog/archives/">归档</a></li>
                <li><a href="/blog/post/link.html">友链</a></li>
                <li><a href="/blog/post/about.html">留言</a></li>
            </ul>
        </nav>
    </div>
    <div id="gkt-cate-nav" class="gkt-header-container hidden-xs">
        
        <nav class="gkt-header-nav float-left">
            <ul>
                
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-learn-interface.html">Golang学习</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-asm-indroduce.html">Golang汇编</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/tcp-three-way-handshake.html">基础</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-how-to-build.html">Golang底层</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left active">
                    <a class="float-left" href="/blog/post/mysql-index-use.html">MySQL</a>
                    
                </li>
                
            </ul>
        </nav>
    </div>
</header>
<div style="height: 44px"></div>
<div class="hidden-xs" style="height: 44px"></div>
<script>
    (function () {
        window.addEventListener('scroll', function () {
            if (window.innerWidth < 768) {
                return;
            }
            var nav = document.querySelector('#gkt-nav');
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            scrollTop > 50 && (nav.classList.add('hide'));
            scrollTop <= 50 && (nav.classList.remove('hide'));
        });
        var cateNavs = document.querySelectorAll('#gkt-cate-nav>nav>ul>li');
        [].slice.call(cateNavs).forEach(function (item) {
            var sub = item.querySelector('.gkt-sub-cate');
            if (!sub) return;
            item.addEventListener('mouseenter', function (e) { sub.style.display = 'block'; }, false);
            item.addEventListener('mouseleave', function (e) { sub.style.display = 'none'; }, false);
        })
    })();
</script>
    <!-- Main Content -->
    <div class="main-container">
        <!-- Main Content -->
<main class="col-xs-12 padding-0 markdown-it">
    <!-- Post Container -->
    
    
    <!-- Post Content -->
<div class="float-left post-container box-shadow">
    <div class="u-arrow-wrapper hidden-xs">
        
        <a class="float-left" href="/blog/post/tcp-what-is-https.html"><i class="u-arrow-left"></i></a>
        
        
        <a class="float-right" href="/blog/post/go-how-to-build.html"><i class="u-arrow-right"></i></a>
        
    </div>
    <article class="col-xs-12">
        <h1> 《高性能MySQL》笔记-索引优化 </h1>

        

        
        <div class="hidden-lg hidden-md series_links">
            <p> <strong> MySQL系列文章链接：</strong></p>
            <ul>
                
                <li>
                    <a href="/blog/post/mysql-index-use.html">《高性能MySQL》笔记-索引优化</a>
                    <span class="post-item-date">(Mar 1, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html">《高性能MySQL》笔记-索引基础</a>
                    <span class="post-item-date">(Mar 2, 2020)</span>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html">EXPLAIN-执行计划查询命令</a>
                    <span class="post-item-date">(Mar 3, 2020)</span>
                </li>
                
            </ul>
        </div>
        
        
        
        <h2 id="示例数据库"><a href="#示例数据库" class="headerlink" title="示例数据库"></a>示例数据库</h2><p>1.下载sakila数据库结构文件和数据</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://dev.mysql.com/doc/index-other.html</span><br></pre></td></tr></table></figure>

<p>2.解压,包含三个文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sakila-schema.sql &#x2F;&#x2F; 数据库结构文件</span><br><span class="line">sakila-data.sql &#x2F;&#x2F; 数据文件</span><br><span class="line">sakila.mwb</span><br></pre></td></tr></table></figure>

<p>3.导入表结构</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; source sakila-schema.sql;</span><br></pre></td></tr></table></figure>

<p>4.导入数据</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; source sakila-data.sql;</span><br></pre></td></tr></table></figure>



<h2 id="建立索引"><a href="#建立索引" class="headerlink" title="建立索引"></a>建立索引</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">--在t100w表中创建一个关于k2列的名为idx_k2的索引</span><br><span class="line">mysql&gt; alter table t100w add index idx_k2(k2);</span><br><span class="line">--唯一索引</span><br><span class="line">mysql&gt; alter table t100w add unique index idx_k1(k1);</span><br><span class="line">--前缀索引</span><br><span class="line">mysql&gt; alter table t100w add index idx_name(name(5));</span><br><span class="line">--联合索引</span><br><span class="line">mysql&gt; alter table t100w add index idx_k1_k2(k1,k2);</span><br></pre></td></tr></table></figure>

<p>## </p>
<h2 id="索引的优点"><a href="#索引的优点" class="headerlink" title="索引的优点"></a>索引的优点</h2><p>1.索引大大减少了服务器需要扫描的数据量<br>2.索引可以帮助服务器避免排序和临时表<br>3.索引可以将随机I/O变为顺序I/O</p>
<blockquote>
<p>索引并不总是最好，索引的维护和查询需要额外的工作，</p>
<p>对于非常小的表，全表扫描更有效。</p>
<p>中到大型的表，索引非常有效，</p>
<p>但是特大型的表，可以选择另外的技术来进行优化（比如分区）</p>
</blockquote>
<h2 id="评价索引是否适合"><a href="#评价索引是否适合" class="headerlink" title="评价索引是否适合"></a>评价索引是否适合</h2><p>索引三星系统：</p>
<p>索引将相关的记录放到一起则获得一星<br>如果索引中的数据顺序和查找中的排序一致则获得二星<br>如果索引中的列包含了查询中需要的全部列则获得三星</p>
<h2 id="高性能索引策略"><a href="#高性能索引策略" class="headerlink" title="高性能索引策略"></a>高性能索引策略</h2><h3 id="独立的列"><a href="#独立的列" class="headerlink" title="独立的列"></a>独立的列</h3><p>“独立的列”是指索引列不能是表达式的一部分，也不能是函数的参数。如果查询中的列不是独立的，则MySQL不会使用索引</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select actor_id from actor where actor_id+1&#x3D;5</span><br><span class="line">select ... where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) &lt;&#x3D;10</span><br></pre></td></tr></table></figure>

<p>因此应<strong>单独</strong>将索引列放在比较符号的一列</p>
<blockquote>
<p>mysql 8.0之后支持函数索引，但是也要先建idx(func(a))，只建idx(a)也是无法使用索引</p>
</blockquote>
<h3 id="前缀索引"><a href="#前缀索引" class="headerlink" title="前缀索引"></a>前缀索引</h3><p>如果需要索引很长的字符列，除了可以选择自建模拟哈希索引，还可以只索引字符列开始的一部分字符。</p>
<p>例如，在增加索引前</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain select * from sakila.film where description like &quot;A Insightful Reflection of a Waitress And a Madman who must Pursue a Boy in Ancient Japan&quot;</span><br></pre></td></tr></table></figure>

<p>查询计划</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/tXTQMNtzNfsS.png?imageslim" alt="mark"></p>
<p>可以看到使用的是全表扫描，但是通过建立前缀索引</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE sakila.film  add key (description(10))</span><br></pre></td></tr></table></figure>

<p>再次查询执行计划就成了这样</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/BEB3QMqpUIyE.png?imageslim" alt="mark"></p>
<p>可以看到使用了description的索引，找到了46条数据，然后再通过跟where条件逐一比对得出结果，这样效率明显比全表扫描快，而且也不用占用太大的空间。</p>
<blockquote>
<p>MySQL无法使用前缀索引做ORDERY BY和GROUP BY，也无法做覆盖扫描</p>
</blockquote>
<p>MySQL原生不支持后缀索引，可通过将字符串反转后存储再建立前缀索引，通过触发器维护这个后缀索引。</p>
<h3 id="索引选择性"><a href="#索引选择性" class="headerlink" title="索引选择性"></a>索引选择性</h3><p> 索引的选择性是指，<strong>不重复的索引值</strong>（也称为基数，cardinality）和数据表的记录总数（#T）的<strong>比值</strong>，范围从1/#T到1之间，<strong>选择性越高则查询效率越高</strong>，因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。</p>
<p>以上面的例子为例，我是以前面10个字符作为前缀建立的索引description(10)，怎么知道应该选用前几个字符来做索引？</p>
<p>可以通过对数据预查询根据索引的选择性来找到合适的前缀数</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SELECT</span><br><span class="line">	count( DISTINCT LEFT ( description, 10 ) ) AS &#39;不重复数cardinality&#39;,</span><br><span class="line">	count( DISTINCT LEFT ( description, 10 ) ) &#x2F; count( * ) AS &#39;索引选择性&#39; </span><br><span class="line">FROM</span><br><span class="line">	film;</span><br></pre></td></tr></table></figure>

<table>
<thead>
<tr>
<th>不重复数cardinality</th>
<th>索引选择性</th>
</tr>
</thead>
<tbody><tr>
<td>47</td>
<td>0.047</td>
</tr>
</tbody></table>
<p>可以看到不重复记录是47，比值是0.047</p>
<p>如果选用<code>LEFT ( description, 5)</code></p>
<table>
<thead>
<tr>
<th>不重复数cardinality</th>
<th>索引选择性</th>
</tr>
</thead>
<tbody><tr>
<td>21</td>
<td>0.021</td>
</tr>
</tbody></table>
<p>可以看到前10个字符的选择性更高，理论上前缀越长选择性越高，但是带来的空间消耗就更大，看实际情况权衡。</p>
<p>同理如果当遇到很多查询条件需要加索引，也可以通过这个count( DISTINCT index_col )先看看这一列的不重复数，选择不重复数高的列来建索引。</p>
<p>对于已有索引也可以通过</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show index from t;</span><br></pre></td></tr></table></figure>

<p>查看索引的选择性</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/HgCEdyTE0cLT.png?imageslim" alt="mark"></p>
<p>可以看到唯一索引和主键因为是不重复的，因此选择性=1（一共1000条记录）</p>
<h3 id="多列索引"><a href="#多列索引" class="headerlink" title="多列索引"></a>多列索引</h3><p>一个常见的错误是为where条件的<strong>每一列</strong>都创建一个<strong>独立</strong>的索引，在5.0以前MySQL只能选择其中的一个索引，在5.0以后引入了一个叫索引合并（index merge）的策略，在一定程度上可以使用多个单列索引来定位行。当使用索引合并的时候EXPLAIN中type会显示index_merge，key列会显示联合哪些索引，Extra列会显示怎么联合了这些索引。</p>
<h4 id="索引合并和联合索引比较"><a href="#索引合并和联合索引比较" class="headerlink" title="索引合并和联合索引比较"></a>索引合并和联合索引比较</h4><p>索引合并很多情况下其实不如联合索引效率高，假设</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM t WHERE col1&#x3D;&#39;ABC&#39; AND col2&#x3D;123</span><br></pre></td></tr></table></figure>

<p>索引合并查询</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/CyyseqBxOG4r.png?imageslim" alt="mark"></p>
<ol>
<li>从col1找到’ABC’列序号15，然后跳到index2找 =123</li>
<li>index2一直找到大于等于15的，没有15直接到32，因此又返回index1</li>
<li>index1找大于等于32，找到32返回该行</li>
<li>找到67，跳到index2找=123 &amp;&amp; rowid=67 找不到</li>
</ol>
<p>可以看到使用索引合并时，需要反复在2个索引表间进行跳转，这是造成速度慢的第一个影响<br>其次，假设满足col1=’ABC’的数据有5行，满足col2=123的数据有1000行。最坏的情况下（那5行在COL2的1000行最后面）需要扫描完col2的1000行才能找到需要的数据，并不能达到快速查找的目的。<br>反之使用组合索引综合保存了col1和col2的数据，不需要在两个索引之间跳转<br>索引合并和组合索引的比较</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/jVDxxPglY0fY.png?imageslim" alt="mark"></p>
<p>小结：多个单列索引在多条件查询时优化器会选择最优索引策略，可能只用一个索引，也可能将多个索引全用上！ 但多个单列索引底层会建立多个B+索引树，比较占用空间，也会浪费一定搜索效率，，因此索引合并只是一个临时折中的方案，当我们看到经常使用的查询，使用索引合并的时候，应该考虑建立联合索引，或者使用IGNORE INDEX忽略某些索引。</p>
<h3 id="合适的索引列顺序"><a href="#合适的索引列顺序" class="headerlink" title="合适的索引列顺序"></a>合适的索引列顺序</h3><p>在一个多列B-Tree索引中，索引列的顺序意味着索引首先按照最左列进行排序，其次是第二列，因此建立索引时应考虑order by 、group by 、distinct 的查询需求。</p>
<p>除此之外，还应考虑将选择性高的索引放在索引的前面，可以使用上面的select语句查询哪一列的索引选择度更高。</p>
<h3 id="聚簇索引"><a href="#聚簇索引" class="headerlink" title="聚簇索引"></a>聚簇索引</h3><p>之前说过聚簇索引性能最好不需要二次查找而且具有唯一性（选择性高），可以根据这个表最常用的SQL查询方式来选择某个字段作为聚簇索引，或组合聚簇索引。</p>
<h3 id="覆盖索引"><a href="#覆盖索引" class="headerlink" title="覆盖索引"></a>覆盖索引</h3><p>如果一个索引包含（或者说覆盖）所有需要查询的字段的值，就称为覆盖索引，覆盖索引必须要存储索引列的值。</p>
<h3 id="使用索引来做排序"><a href="#使用索引来做排序" class="headerlink" title="使用索引来做排序"></a>使用索引来做排序</h3><p>如果EXPLAIN出来的type列的值为“index”，则说明MySQL使用了索引扫描来做排序，只有当索引的列顺序和ORDER BY 子句的顺序完全一致，并且所有排序方向一致时才能使用索引排序。</p>
<p>使用延迟关联优化使用索引排序</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">select * from profiles </span><br><span class="line">INNER JOIN (</span><br><span class="line">SELECT primary_key_col FROM profiles </span><br><span class="line">where idx_col1&#x3D;&#39;&#39; ORDER BY idx_col2 LIMIT 100,10</span><br><span class="line">) AS x USING (primary_key_col);</span><br></pre></td></tr></table></figure>

<p>使用覆盖索引找到主键，在通过主键关联原表的行，减少MySQL因为排序而扫描那些需要丢弃的行数</p>
<h3 id="重用现有的索引"><a href="#重用现有的索引" class="headerlink" title="重用现有的索引"></a>重用现有的索引</h3><p>索引的建立和维护都需要一定的代价，我们应该尽可能重用索引而不是建立大量的组合索引。</p>
<p>可以适当使用IN()来绕开，最左匹配限制。</p>
<p>如sex和country在我们的数据里面可能选择性很低但是经常会用到，可能就要建立大量的索引，其实我们可以通过建立idx(sex,country,regin,age)这样可以覆盖更多的场景，然后可以通过使用 sex IN (“男”,”女”) AND country =’XXX’ AND regin = ‘XXX’来对 sex 条件进行全覆盖，这样既可以用到索引，又能减少索引数量。</p>
<h3 id="减少冗余-重复-未使用的索引"><a href="#减少冗余-重复-未使用的索引" class="headerlink" title="减少冗余/重复/未使用的索引"></a>减少冗余/重复/未使用的索引</h3><h2 id="慢查询优化"><a href="#慢查询优化" class="headerlink" title="慢查询优化"></a>慢查询优化</h2><h3 id="为什么查询速度会慢"><a href="#为什么查询速度会慢" class="headerlink" title="为什么查询速度会慢"></a>为什么查询速度会慢</h3><p>1.如果要优化查询，实际上要优化其子任务，要么消除其中一些子任务，要么减少子任务的执行次数，要么让子任务运行得更快<br>2.查询的生命周期大致可以按照顺序来看：从客户端，到服务器，然后在服务器上进行解析，生成执行计划，执行，并返回结果给客户端</p>
<h3 id="慢查询基础：优化数据访问"><a href="#慢查询基础：优化数据访问" class="headerlink" title="慢查询基础：优化数据访问"></a>慢查询基础：优化数据访问</h3><p>查询性能低下最基本的原因是访问的数据太多，可通过以下两个步骤分析</p>
<h4 id="1-确认应用程序是否在检索大量超过需要的数据"><a href="#1-确认应用程序是否在检索大量超过需要的数据" class="headerlink" title="1.确认应用程序是否在检索大量超过需要的数据"></a>1.确认应用程序是否在检索大量超过需要的数据</h4><ul>
<li>查询不需要的记录</li>
</ul>
<p>实际只要一条或者几条数据但是没有使用limit</p>
<ul>
<li>多表关联并返回全部列</li>
</ul>
<p>连表的时候会按索引进行关联，但是取数据是需要回表查询的，因此尽量只返回你需要的那个表的数据 <code>select table1.*</code>，<code>不要select *</code></p>
<ul>
<li>总是取出全部列</li>
</ul>
<p>尽量只取你需要的字段</p>
<ul>
<li>重复查询相同的数据</li>
</ul>
<p>不断执行同样的查询，可以考虑使用缓存</p>
<h4 id="2-是否向数据库请求扫描了不需要的数据"><a href="#2-是否向数据库请求扫描了不需要的数据" class="headerlink" title="2.是否向数据库请求扫描了不需要的数据"></a>2.是否向数据库请求扫描了不需要的数据</h4><p>查询开销的三个衡量指标</p>
<ul>
<li>响应时间</li>
</ul>
<p><strong>服务时间和排队时间</strong>之和，通过“快速上限估计”法评估，概括地说，了解查询需要哪些索引以及它的执行计划，计算总消耗</p>
<ul>
<li>扫描的行数</li>
<li>返回的行数</li>
</ul>
<h4 id="三种方式应用WHERE条件"><a href="#三种方式应用WHERE条件" class="headerlink" title="三种方式应用WHERE条件"></a>三种方式应用WHERE条件</h4><p>从好到坏</p>
<p>1.在索引中使用WHERE条件来过滤不匹配的记录；</p>
<p>2.使用索引覆盖扫描（Extra中出现Using index）来返回记录，直接从索引中过滤不需要的记录并返回命中结果</p>
<p>3.从数据表中返回数据，然后过滤不满足条件的记录（Extra中出现Using Where）<br>需要扫描大量数据但只返回少数的行的优化技巧：使用索引覆盖扫描，改变库表结构(使用单独汇总表)，重写复杂的查询</p>
<h3 id="重写复杂查询"><a href="#重写复杂查询" class="headerlink" title="重写复杂查询"></a>重写复杂查询</h3><p>MySQL从设计上让连接和断开连接都很轻量级，在返回一个小的查询结果方面很高效</p>
<h4 id="切分查询"><a href="#切分查询" class="headerlink" title="切分查询"></a>切分查询</h4><p>将大查询切分成小查询，每个查询功能完全一样，只完成一小部分，每次只返回一小部分查询结果，可以避免锁住很多数据、占满事务日志、耗尽系统资源、阻塞很多小的但重要的查询。如删除数据可以每次删除一小部分，循环几次。</p>
<h4 id="分解关联查询"><a href="#分解关联查询" class="headerlink" title="分解关联查询"></a>分解关联查询</h4><p>对每个表进行单表查询，然后将结果在应用进行关联。</p>
<p>优势：</p>
<p>让缓存的效率更高<br>将查询分解后，执行单个查询可以减少锁的竞争<br>在应用层做关联，可以更容易对数据库进行拆分，更容易做到高性能和可扩展<br>查询本身效率也可能会有所提升<br>可以减少冗余记录的查询<br>相当于在应用中实现了哈希关联，而不是使用MySQL的嵌套循环关联</p>
<p>应用场景：</p>
<p>当应用能够方便地缓存单个查询的结果的时候<br>当可以将数据分布到不同的MySQL服务器上的时候<br>当能够使用IN()的方式代替关联查询的时候<br>当查询中使用同一个数据表的时候</p>
<h3 id="查询执行的基础"><a href="#查询执行的基础" class="headerlink" title="查询执行的基础"></a>查询执行的基础</h3><h4 id="查询执行路径"><a href="#查询执行路径" class="headerlink" title="查询执行路径"></a>查询执行路径</h4><p><img src="http://river.marvelthanos.com/blog/20200329/kLFrn7oOfa4t.png?imageslim" alt="mark"></p>
<ol>
<li>客户端发送一条查询给服务器</li>
<li>服务器先检查查询缓存，如果命中则立刻返回，否则进入下一阶段</li>
<li>服务器端进行SQL解析、预处理，再由优化器生成对应的执行计划</li>
<li>MySQL根据优化器生成的执行计划，调用存储引擎的API来执行查询</li>
<li>将结果返回给客户端</li>
</ol>
<h4 id="MySQL客户端-服务端通信协议"><a href="#MySQL客户端-服务端通信协议" class="headerlink" title="MySQL客户端/服务端通信协议"></a>MySQL客户端/服务端通信协议</h4><ul>
<li><p>MySQL客户端和服务器之间的通信协议是“半双工”的，无法将一个消息切成小块独立来发送，没法进行流量控制，一旦一端开始发生消息，另一端要接收完整个消息才能响应它</p>
</li>
<li><p>MySQL通常需要等所有的数据都已经发送给客户端才能释放这条查询所占用的资源，所以接收全部结果并缓存通常可以减少服务器的压力</p>
</li>
<li><p>查询状态</p>
<p>对于一个MySQL连接，或者说一个线程，任何时可都有一个状态，该状态表示了MySQL正在做什么，可以通过</p>
<p><code>SHOW FULL PROCESSLIST</code>命令查看：</p>
</li>
</ul>
<blockquote>
<p>Sleep，线程正在等待客户端发送新的请求<br>Query，线程正在执行查询或者正在将结果发送给客户端<br>Locked，在MySQL服务器层，该线程正在等待表锁<br>Analyzing and statistics，线程正在收集存储引擎的统计信息，并生成查询的执行计划<br>Copying to tmp table [on disk]，线程正在执行查询，并且将其结果集都复制到一个临时表中，要么是在做GROUP BY操作，要么是文件排序操作，或者是UNION操作<br>Sorting result，线程正在对结果集进行排序<br>Sending data，线程可能在多个状态之间传送数据，或者在生成结果集，或者在向客户端返回数据</p>
</blockquote>
<h4 id="查询缓存"><a href="#查询缓存" class="headerlink" title="查询缓存"></a>查询缓存</h4><p>如果查询缓存打开，MySQL会优先检查这个查询是否命中缓存，通过一个对大小写敏感的哈希查找实现，因此即使只有一个字节不同也不能命中缓存，持此之外，在返回查询结果之前MySQL会检查一次用户权限，确认有该表权限再返回。</p>
<h4 id="查询优化处理"><a href="#查询优化处理" class="headerlink" title="查询优化处理"></a>查询优化处理</h4><h5 id="语法解析器和预处理"><a href="#语法解析器和预处理" class="headerlink" title="语法解析器和预处理"></a>语法解析器和预处理</h5><p>MySQL通过关键字将SQL语句进行解析，并生成一棵对应的“解析树”，解析器将使用MySQL语法规则验证和解析查询，预处理器则根据一些MySQL规则进一步检查解析树是否合法</p>
<h5 id="查询优化器"><a href="#查询优化器" class="headerlink" title="查询优化器"></a>查询优化器</h5><p>如果解析树合法就会生成执行计划，MySQL使用基本成本的优化器，将尝试预测一个查询使用某种执行计划时的成本，并选择其中成本最小的一个，</p>
<p>使用<code>SHOW STATUS LIKE &#39;Last_query_cost&#39;;</code></p>
<p>查看需要多少个数据页的随机查找<br>导致MySQL查询优化器选择错误的原因：</p>
<ul>
<li>统计信息不准确，Innodb不能维护一个数据表的行数的精确统计信息</li>
<li>执行计划中的成本估算不等同于实际执行的成本</li>
<li>MySQL的最优可能和你想的最优不一样</li>
<li>MySQL从不考虑其他并发执行的查询</li>
<li>MySQL也并不是任何时候都是基于成本的优化</li>
<li>MySQL不会考虑不受其控制的操作的成本</li>
<li>优化器有时候无法去估算所有可能的执行计划</li>
</ul>
<h5 id="MySQL能处理的优化类型："><a href="#MySQL能处理的优化类型：" class="headerlink" title="MySQL能处理的优化类型："></a>MySQL能处理的优化类型：</h5><ul>
<li>重新定义关联表的顺序</li>
<li>将外链接转化成内链接</li>
<li>使用等价变换规则</li>
</ul>
<p>例如 （5=5 AND a&gt;5）优化成（a&gt;5）, (a &lt; b AND b=c) AND a=5 优化成 b &gt; 5 AND b=c AND a=5。 </p>
<ul>
<li>优化COUNT()、MIN()和MAX()</li>
</ul>
<p>例如找到某一列的最小值只要知道对应B-Tree索引的最左端的记录。在EXPLAIN中可以看到“Select tables optimized away”</p>
<ul>
<li><p>预估并转化为常数表达式，当检测到一个表达式可以转化为常数的时候，就会一直把该表达式作为常数进行优化处理</p>
</li>
<li><p>覆盖索引扫描，当索引中的列包含所有查询中需要使用的列的时候，就可以使用索引返回需要的数据，而无须查询对应的数据行</p>
</li>
<li><p>子查询优化</p>
</li>
<li><p>提前终止查询，在发现已经满足查询需求的时候，MySQL总是能够立刻终止查询</p>
</li>
<li><p>等值传播，如果两个列的值通过等式关联，那么MySQL能够把其中一个列的WHERE条件传递到另一列上</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SELECT film.film_id FROM sakila.film</span><br><span class="line">	INNER JOIN sakila.film_actor USING(film_id) </span><br><span class="line">WHERE film.film_id &gt;500</span><br></pre></td></tr></table></figure>

<p>MySQL知道film_id不仅对film有用，对于film_actor同样有用，因此会优化成</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">WHERE film.film_id &gt;500 and film_actor.film_id&gt;500</span><br></pre></td></tr></table></figure>
</li>
<li><p>列表IN()的比较，MySQL将IN()列表中的数据先进行排序，然后通过二分查找的方式来确定列表中的值是否满足条件</p>
</li>
</ul>
<p>在服务器层有查询优化器，却没有保存数据和索引的统计信息，统计信息由存储引擎实现，不同的存储引擎可能会存储不同的统计信息<br>在MySQL中，每一个查询，每一个片段（包括子查询，甚至基于单表的SELECT）都可能是关联<br>对于UNION查询，MySQL先将一系列的单个查询结果放到一个临时表中，然后再重新读出临时表数据来完成UNION查询<br>MySQL对任何关联都执行“嵌套循环关联”操作，即MySQL先在一个表中循环取出单条数据，然后再嵌套到下一个表中寻找匹配的行，依次下去，直到找到所有表中匹配的行为止</p>
<p><img src="http://river.marvelthanos.com/blog/20200330/KUVXu8xJiEKl.png?imageslim" alt="mark"></p>
<p>当进行多表关联时总是从一个表开始一直嵌套循环</p>
<p><img src="http://river.marvelthanos.com/blog/20200330/cB4doqdr3pI6.png?imageslim" alt="mark"></p>
<p>全外连接就无法通过嵌套循环和回溯的方式完成，当发现关联表中没有找到任何匹配行的时候，则可能是因为关联恰好从一个没有任何匹配的表开始，MySQL不支持全外连接</p>
<h5 id="关联查询优化器"><a href="#关联查询优化器" class="headerlink" title="关联查询优化器"></a>关联查询优化器</h5><p>会尝试在所有的关联顺序中选择一个成本最小的来生成执行计划树，如果可能，优化器会遍历每一个表然后逐个做嵌套循环计算每一棵可能的执行树的成本，最后返回一个最优的执行计划<br>如果有超过n个表的关联，那么需要检查n的阶乘关联顺序，称为“搜索空间”，搜索空间的增长速度非常快</p>
<h5 id="排序优化"><a href="#排序优化" class="headerlink" title="排序优化"></a>排序优化</h5><p>无论如何排序都是一个成本很高的操作，所以从性能角度考虑，应尽可能避免排序或者尽可能避免对大量数据进行排序<br>当不能使用索引生成排序结果的时候，MySQL需要自己进行排序，如果数据量小则在内存中进行，如果数据量大则需要使用磁盘，MySQL将这个过程称为文件排序（filesort），即使完全是内存排序不需要任何磁盘文件时也是如此。</p>
<p>当排序的数据量小于“排序缓冲区”，MySQL使用内存进行“快速排序”操作，当内存不够排序就会将数据分块，对每个独立的块使用“快速排序”进行排序，并将各个块的排序结果放在磁盘上，然后将各个排好序的块进行合并（merge）</p>
<h5 id="MySQL两种排序策略"><a href="#MySQL两种排序策略" class="headerlink" title="MySQL两种排序策略"></a>MySQL两种排序策略</h5><p><strong>两次传输排序</strong></p>
<p>读取<strong>行指针</strong>和需要排序的字段，对其进行排序，然后再根据排序结果读取所需的数据行。二次查找，传输成本高，省内存。</p>
<p><strong>单次传输排序</strong></p>
<p>先按顺序I/O读取所需要的所有列，再根据给定列进行排序，最后返回结果。</p>
<p>传输成本低，内存占用大，每条记录按能存放的最大值来分配空间（VARCHAR）</p>
<h4 id="MySQL查询优化器的局限性"><a href="#MySQL查询优化器的局限性" class="headerlink" title="MySQL查询优化器的局限性"></a>MySQL查询优化器的局限性</h4><p>1.关联子查询：MySQL的子查询实现得非常糟糕，最糟糕的一类查询是WHERE条件中包含IN()的子查询语句，使用EXISTS()来改写</p>
<p><img src="http://river.marvelthanos.com/blog/20200329/z0xbNjdvmRI4.png?imageslim" alt="mark"></p>
<p>2.UNION的限制：</p>
<p>有时，MySQL无法将限制条件从外层“下推”到内层，这使得原本能够限制部分返回结果的条件无法应用到内层查询的优化上,因此那些order by ，limit 之类的语句应该手动下推到各个UNION查询内部。<br>3.MySQL无法利用多核特性来并行执行查询<br>4.MySQL不支持哈希关联，MariaDB已经实现了哈希关联<br>5.MySQL不支持松散索引扫描，5.0后版本在分组查询中需要找到分组的最大值和最小值时可以使用松散索引扫描<br>6.对于MIN()和MAX()查询，MySQL的优化做得并不好</p>
<h3 id="优化特定类型的查询"><a href="#优化特定类型的查询" class="headerlink" title="优化特定类型的查询"></a>优化特定类型的查询</h3><h4 id="优化COUNT-查询"><a href="#优化COUNT-查询" class="headerlink" title="优化COUNT()查询"></a>优化COUNT()查询</h4><p>COUNT()是一个特殊的函数，有两种非常不同的作用：可以统计某个列值的数量，也可以统计行数，在统计列值时要求列值是非空的（不统计NULL）<br>COUNT(*)并不是会像我们猜想的那样扩展成所有的列，实际上，它会忽略所有的列而直接统计所有的行数，当MySQL确认括号内的表达值不可能为空时，实际上就是在统计行数<br>MyISAM的COUNT()函数只有<strong>没有任何WHERE条件</strong>下的COUNT(*)才非常快。</p>
<p>优化：<br>使用近似值，如EXPLAIN出来的优化器估算行数<br>使用索引覆盖<br>使用汇总表<br>使用外部缓存系统</p>
<h4 id="优化关联查询"><a href="#优化关联查询" class="headerlink" title="优化关联查询"></a>优化关联查询</h4><p>1.确保on或者using子句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。</p>
<p>当表A和表B用列c关联的时候，如果优化器关联的顺序是A、B，那么就<strong>不需要</strong>在A表的对应列上创建索引。没有用到的索引会带来额外的负担，一般来说，除非有其他理由，只需要在关联顺序中的<strong>第二张表</strong>的相应列上创建索引（具体原因下文分析）。</p>
<p>2.确保任何的group by 和order by 的表达式只涉及到一个表中的列。这样mysql才有可能使用索引来优化这个过程。</p>
<p>如果ORDER BY子句所有列都来自关联的第一个表，那在关联处理第一个表的时候就进行文件排序。此时Extra字段会有“Using filesort”。除此之外所有情况，MySQL都会先将关联的结果放到一个临时表，然后所有关联结束后再进行文件排序，此时Extra字段是“Using temporary；Using filesort”。</p>
<p>3.当升级mysql的时候需要注意：关联语法，运算符优先级等其他可能会发生变化的地方。</p>
<p>关联查询示例说明：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SELECT A.xx,B.yy   </span><br><span class="line">FROM A INNER JOIN B USING(c)  </span><br><span class="line">WHERE A.xx IN (5,6)</span><br></pre></td></tr></table></figure>

<p>假设MySQL按照查询中的关联顺序A、B来进行关联操作，那么可以用下面的伪代码表示MySQL如何完成这个查询：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">outer_iterator &#x3D; SELECT A.xx,A.c FROM A WHERE A.xx IN (5,6);  </span><br><span class="line">outer_row &#x3D; outer_iterator.next;  </span><br><span class="line">while(outer_row) &#123;  </span><br><span class="line">    inner_iterator &#x3D; SELECT B.yy FROM B WHERE B.c &#x3D; outer_row.c;  </span><br><span class="line">    inner_row &#x3D; inner_iterator.next;  </span><br><span class="line">    while(inner_row) &#123;  </span><br><span class="line">        output[inner_row.yy,outer_row.xx];  </span><br><span class="line">        inner_row &#x3D; inner_iterator.next;  </span><br><span class="line">    &#125;  </span><br><span class="line">    outer_row &#x3D; outer_iterator.next;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可以看到，最外层的查询是根据A.xx列来查询的，A.c上如果有索引的话，整个关联查询也<strong>不会使用</strong>。再看内层的查询，很明显B.c上如果有索引的话，<strong>能够加速查询</strong>，因此只需要在关联顺序中的第二张表的相应列上创建索引即可。</p>
<h4 id="优化子查询"><a href="#优化子查询" class="headerlink" title="优化子查询"></a>优化子查询</h4><p>尽可能使用关联查询代替，是因为 MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。如果使用MySQL5.6以上或MariaDB则可以忽略这个建议</p>
<h4 id="优化GROUP-BY和DISTINCT"><a href="#优化GROUP-BY和DISTINCT" class="headerlink" title="优化GROUP BY和DISTINCT"></a>优化GROUP BY和DISTINCT</h4><p>使用索引优化<br>当无法使用索引时，GROUP BY使用两种策略来完成：使用临时表或者文件排序来做分组<br>尽可能的将WITH ROLLUP（超级聚合）功能移动应用程序中处理</p>
<h4 id="优化LIMIT分页"><a href="#优化LIMIT分页" class="headerlink" title="优化LIMIT分页"></a>优化LIMIT分页</h4><p>最简单的办法是尽可能地使用索引覆盖扫描，而不是查询所有的列，然后根据需要做一次关联操作再返回所需的列，</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select id,name,…… from table </span><br><span class="line">	innert join (select id from table order by xxx limit 5000,5) as table1 USING(id);</span><br></pre></td></tr></table></figure>

<p>offset会导致MySQL扫描大量不需要的行然后再抛弃掉</p>
<p>如果可以记录上次取数据的位置，下次就可以直接从该记录的位置开始扫描，可以避免使用offset</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM users WHERE uid &gt; 456891 ORDER BY uid LIMIT 0, 10;</span><br></pre></td></tr></table></figure>

<p>使用预先计算的汇总表，或者关联到一个冗余表</p>
<h4 id="优化UNION查询"><a href="#优化UNION查询" class="headerlink" title="优化UNION查询"></a>优化UNION查询</h4><p>通过创建并填充临时表的方式来执行UNION查询，因此很多优化策略在UNION查询中都没法很好地使用，经常需要手工地将WHERE、LIMIT、ORDER BY等子句下推到UNION的各个子查询中<br>除非确实需要服务器消除重复的行，否则就一定要使用UNION ALL</p>
<p>如果没有ALL关键字，mysql会给临时表加上distinct选项，这会导致对整个临时表的数据唯一检查。这样的代价非常高。即使有ALL关键字，mysql仍然会使用临时表存储结果。<br>事实上，mysql总是将结果放入临时表，然后再读出，再返回给客户端，很多时候这样做是没有必要的。</p>
<h2 id="MySQL8-0-13新支持索引特性"><a href="#MySQL8-0-13新支持索引特性" class="headerlink" title="MySQL8.0.13新支持索引特性"></a>MySQL8.0.13新支持索引特性</h2><h3 id="1-隐藏索引"><a href="#1-隐藏索引" class="headerlink" title="1.隐藏索引"></a>1.隐藏索引</h3><p>特点：不会被优化器所使用，但仍然需要进行维护。</p>
<p>应用场景：软删除、灰度发布。</p>
<p>软删除： 需要进行索引的删除时，现在只能删除后查看效果，但是删除后发现索引不应该被删除，那只能再创建出来，这个过程是非常耗性能的。这时我们可以使用隐藏索引，先设置为隐藏，等确认删除没有影响之后再进行真正的删除。</p>
<p>使用：</p>
<p>创建隐藏索引的关键字是invisible， 在正常创建索引的最后加上invisible字段即可。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">-- 新创建隐藏索引</span><br><span class="line">create index idx_j on t(j) invisible; </span><br><span class="line">-- 将现有索引设置成隐藏索引</span><br><span class="line">alter table t alter index idx_j invisible;  </span><br><span class="line">-- 设置成非隐藏索引</span><br><span class="line">alter table t alter index idx_j visible;</span><br></pre></td></tr></table></figure>

<h3 id="2-函数索引"><a href="#2-函数索引" class="headerlink" title="2.函数索引"></a>2.函数索引</h3><p>在函数索引中使用函数(表达式)的值</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">-- 创建一个upper的函数索引</span><br><span class="line">create index idx_2 on t((UPPER(c2))); </span><br><span class="line">-- 查询</span><br><span class="line">select * from t where UPPER(c2)&#x3D;&#39;abc&#39;</span><br></pre></td></tr></table></figure>

<h3 id="3-降序索引"><a href="#3-降序索引" class="headerlink" title="3.降序索引"></a>3.降序索引</h3><p>mysql8.0开始真正支持降序索引，之前也可以创建降序索引，但是他创建的实际还是升序索引。</p>
<p>只有InnoDB存储引擎支持降序索引，只支持BTREE降序索引。</p>
<p>不再对GROUP BY操作进行隐式排序。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alter table t100w add index idx_k1_k2(k1 asc,k2 desc);</span><br></pre></td></tr></table></figure>

<h3 id="4-json数据的索引"><a href="#4-json数据的索引" class="headerlink" title="4.json数据的索引"></a>4.json数据的索引</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">create table t(data json, index((CAST(data-&gt;&gt;&#39;$.name&#39; as char(30)))));</span><br></pre></td></tr></table></figure>

<p> cast是类型转换，也就是说把data中的name列转换成一个char(30)的列   </p>
<p><strong><code>-&gt;&gt;</code></strong> 一个新的运算符。</p>
<p>对应的查询也要按这个格式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain select * from t where CAST(data-&gt;&gt;&#39;$.name&#39; as char(30)) &#x3D; &#39;axx&#39;</span><br></pre></td></tr></table></figure>





<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>高性能MySQL第三版</p>
<p><a href="https://blog.csdn.net/ltylove2007/article/details/21084809" target="_blank" rel="noopener">索引合并和组合索引的比较</a></p>
<p><a href="https://www.jianshu.com/p/c75c72e5e3bc" target="_blank" rel="noopener">mysql8.0——索引优化</a></p>

        
<script src="/blog/js/qrious.min.js"></script>


        <hr>
        <div>
            
            <p>
                <span>专题: </span>
                
                <a href="/blog/series/#MySQL">
                    <code key="MySQL" class="post-label">MySQL</code>
                </a>
                
            </p>
            
            <p>
                <span>本文发表于 2020-03-01，最后修改于 2020-03-31。</span>
            </p>
            <!-- 文章末尾的提示 start -->
            
            
            
                
            <!-- 文章末尾的提示 end -->
        </div>
        <hr />
        <p name="pagination" style="font-size: 1.2em">
    
    <a class="float-left" href="/blog/post/tcp-what-is-https.html">上一篇 « 简述HTTPS/SSL/TLS运行过程</a>
    
    
    <a class="float-right" href="/blog/post/go-how-to-build.html">下一篇 » Golang程序编译执行流程</a>
    
</p>
    </article>
    <!-- 赞赏 -->
    <!--打赏-->

    <!-- 推荐阅读三篇文章 -->
    <div class="col-xs-12">
        <h3>推荐阅读</h3>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/tcp/tcp.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#基础"
                    title="基础">基础</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#tcp/http"
                    title="tcp/http">tcp/http</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/tcp-three-way-handshake.html" class="title">
                简述TCP“3次握手，4次挥手”
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-02-21，</span>
                    <span class="hidden-xs">全文9978字，</span>
                    <span>阅读约34分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/goasm/2.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang汇编"
                    title="Golang汇编">Golang汇编</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang底层"
                    title="Golang底层">Golang底层</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang"
                    title="Golang">Golang</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/go-asm-method.html" class="title">
                Golang汇编(二)-函数调用
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-02-16，</span>
                    <span class="hidden-xs">全文8373字，</span>
                    <span>阅读约28分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/about.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#关于我"
                    title="关于我">关于我</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/about.html" class="title">
                关于我
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2010-02-03，</span>
                    <span class="hidden-xs">全文199字，</span>
                    <span>阅读约1分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
    </div>
    <div class="col-xs-12">
        <!-- 标签列表 -->
        <!-- Featured Tags -->
<style>
    #featured-tag .post-tag-item {
        font-size: 12px;
        line-height: 30px;
        display: inline-block;
        height: 30px;
        margin: 5px 0px;
        padding: 0 7px;
        color: #333;
        border-radius: 15px;
        background: #f6f6f6;
    }

    #featured-tag .post-tag-item:hover {
        color: #337ab7;
    }
</style>
<div id="featured-tag">
    
    <a class="post-tag-item" href="/blog/tags/#关于我" title="关于我"
        rel="1">#关于我 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#友链" title="友链"
        rel="1">#友链 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang汇编" title="Golang汇编"
        rel="4">#Golang汇编 (4) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang底层" title="Golang底层"
        rel="5">#Golang底层 (5) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang" title="Golang"
        rel="8">#Golang (8) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang基础" title="Golang基础"
        rel="1">#Golang基础 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang源码" title="Golang源码"
        rel="2">#Golang源码 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#基础" title="基础"
        rel="2">#基础 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#tcp/http" title="tcp/http"
        rel="2">#tcp/http (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL" title="MySQL"
        rel="3">#MySQL (3) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL优化" title="MySQL优化"
        rel="1">#MySQL优化 (1) </a>
    
</div>
    </div>
    <!-- 评论 -->
    <div class="col-xs-12">
        
    </div>
</div>
<aside class="float-left gkt-sidebar hidden-xs hidden-sm">
    <div style="clear: both"></div>
    <div class="gkt-sidebar-wrapper">
        <section class="box-shadow"><style>
    .gkt-summary {
        border: 1px solid #DDDDDD;
        border-radius: 3px;
        padding: 5px;
        width: 100%;
    }


    .gkt-summary nav {
        overflow: hidden;
    }

    .gkt-summary nav a {
        display: inline-block;
        text-align: center;
        color: #333;
        font-size: 12px;
    }

    .gkt-summary nav span {
        display: block;
    }

    .gkt-summary nav .middle {
        border-left: 1px solid #eaecef;
        border-right: 1px solid #eaecef;
    }

    .gkt-summary .number {
        font-weight: bold;
    }

    .gkt-summary .link-list {
        margin-top: 5px;
        margin-bottom: -5px;
        padding-top: 7px;
        border-top: 1px dashed #999;
        display: flex;
    }

    .gkt-summary .link-list a {
        flex: 1;
    }

    .gkt-summary .link-list img {
        width: 25px;
        height: 25px;
    }
</style>

<div class="gkt-summary">
    <nav>
        <a href="/blog/" class="col-xs-4">
            <number class="number">15</number><span>文章</span>
        </a>
        <a href="/blog/series" class="col-xs-4 middle">
            <number class="number">5</number><span>专题</span>
        </a>
        <a href="/blog/tags" class="col-xs-4">
            <number class="number">11</number><span>标签</span>
        </a>
    </nav>

    
    <div class="link-list">
        
        <a href="https://marvelthanos.com" target="_blank" rel="noopener"><img src="/blog/img/related_links/thanos.webp" /></a>
        
    </div>
    
</div></section>
        
        
        <section class="gkt-sidebar-content box-shadow">
            <strong>MySQL</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/mysql-index-use.html"
                        class="gkt-sidebar-active">《高性能MySQL》笔记-索引优化</a>
                    
                    <!-- Table of Contents -->
<div id="sidebar-toc">
  <!-- TOC  -->
  
  <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#示例数据库"><span class="toc-nav-text">示例数据库</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#建立索引"><span class="toc-nav-text">建立索引</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#索引的优点"><span class="toc-nav-text">索引的优点</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#评价索引是否适合"><span class="toc-nav-text">评价索引是否适合</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#高性能索引策略"><span class="toc-nav-text">高性能索引策略</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#慢查询优化"><span class="toc-nav-text">慢查询优化</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#MySQL8-0-13新支持索引特性"><span class="toc-nav-text">MySQL8.0.13新支持索引特性</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#参考资料"><span class="toc-nav-text">参考资料</span></a></li></ol>
  
</div>

<script>
  (function () {
    var h2 = document.querySelectorAll('article h2');
    var h3 = document.querySelectorAll('article h3');
    var linkList = document.querySelectorAll('#sidebar-toc a');

    function findLinkElement(name) {
      for (var i = 0; i < linkList.length; i++) {
        var items = linkList[i].href.split('#');
        if (items && items[items.length - 1] === encodeURIComponent(name)) {
          return i;
        }
      }
      return -1;
    }

    function activeLink(titleList) {
      var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      for (var i = titleList.length - 1; i >= 0; i--) {
        if (scrollTop - titleList[i].offsetTop > 0) {
          var index = findLinkElement(titleList[i].id);
          index != -1 && linkList[index].classList.add('gkt-sidebar-active');
          break;
        }
      }
    }

    window.addEventListener("scroll", function (e) {
      [].slice.call(linkList).forEach(function (link) {
        link.classList.remove('gkt-sidebar-active');
      })
      activeLink(h2);
    })
  })();
</script>
                    
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html"
                        class="">《高性能MySQL》笔记-索引基础</a>
                    
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html"
                        class="">EXPLAIN-执行计划查询命令</a>
                    
                </li>
                
            </ul>
        </section>
        
        
        <section class="box-shadow">
            <strong>最近的文章</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html">Go源码学习 sort包</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html">EXPLAIN-执行计划查询命令</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html">《高性能MySQL》笔记-索引基础</a>
                </li>
                
            </ul>
        </section>
        <section class="box-shadow text-center">
            
        </section>
    </div>
</aside>

<script>
    (function () {
        var ele = getDom('.gkt-sidebar-content')
        var wrapper = getDom('.gkt-sidebar-wrapper')
        var last = 0
        window.addEventListener("scroll", function (e) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var isDown = scrollTop > last;
            var pos = getPosition(ele).y - scrollTop;
            var downLimit = 50;
            var upLimit = -100;
            // uarrow.style.marginTop = scrollTop + 'px';
            isDown && pos <= downLimit && wrapper.classList.add("gkt-sidebar-fixed");
            !isDown && pos > upLimit && wrapper.classList.remove("gkt-sidebar-fixed");
            last = scrollTop
        })
    })();
    (function () {
        function resizeUArrow() {
            var s = getDom('.u-arrow-wrapper').style
            var pc = getDom('.post-container')
            s.left = getPosition(pc).x + 'px';
            s.width = pc.clientWidth + 'px';
        }
        resizeUArrow()
        window.addEventListener('resize', resizeUArrow);
    })();
</script>

    
</main>
    </div>
    <style>
    img#go-top {
        position: fixed;
        bottom: 100px;
        width: 50px;
        cursor: pointer;
        z-index: 9999;
    }
</style>
<img id="go-top" src="/blog/icon/top.png" class="hidden-xs" style="display: none" />
<script>
    (function () {
        var goTop = document.getElementById('go-top');
        var mainContainer = document.querySelector('.main-container');
        
        goTop.addEventListener('click', function () {
            window.scroll(0, 0);
        }, false);
        window.addEventListener('scroll', function () {
            var right = document.body.offsetWidth - mainContainer.getBoundingClientRect().right;
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            goTop.style.right = right + 10 + 'px'
            scrollTop > 700 && (goTop.style.display = "block");
            scrollTop <= 700 && (goTop.style.display = "none");
        });
    })();
</script>
    <style>
    #geektutu-click-img-container {
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        display: none;
        z-index: 9999;
    }

    #geektutu-click-img-container img {
        object-fit: contain;
        background: #eaecef;
        padding: 15px;
        border-radius: 10px;
        height: auto;
        width: auto;
        vertical-align: middle;
    }
</style>


<div id="geektutu-click-img-container">
    <img src="" alt="Big Image">
</div>

<script>
    (function () {
        var container = document.querySelector('#geektutu-click-img-container')
        var targetImg = container.querySelector('img')
        var imgs = document.querySelectorAll('article img');
        targetImg.addEventListener('click', function (e) {
            container.style.display = 'none';
            e && e.stopPropagation && e.stopPropagation();
        }, false);

        for (var i = 0; i < imgs.length; ++i) {
            var img = imgs[i];
            img.addEventListener('click', (function (src, rate) {
                return function (e) {
                    e && e.stopPropagation && e.stopPropagation();
                    if (window.innerWidth < 980) {
                        return
                    }
                    targetImg.style.height = targetImg.style.width = 'auto';
                    if (window.innerWidth / window.innerHeight > rate) {
                        targetImg.style.height = (window.innerHeight - 20) + 'px';
                    } else {
                        targetImg.style.width = (window.innerWidth - 20) + 'px';
                    }
                    container.style.height = window.innerHeight + 'px'
                    container.style.lineHeight = window.innerHeight + 'px'
                    container.style.display = 'block';
                    targetImg.src = src;
                };
            }(img.src, img.width / img.height)), false)
        }
    })();
</script>
    <!-- Footer -->
    <!-- Footer -->
<style>
    footer {
        width: 100%;
        line-height: 1.5;
        padding: 20px;
    }

    footer a {
        color: #333;
        text-decoration: none;
    }

    .footer-hexo img {
        height: 20px;
        margin-bottom: -5px;
    }

    .footer-hexo a {
        color: #337ab7;
    }
</style>
<footer class="text-center col-xs-12">
    <p>
        <small>© 2020 - Evrn - </small>
        <small>
            <a target="_blank" rel="nofollow noopener" href="http://www.miitbeian.gov.cn/"></a>
        </small>
    </p>
    <p class="footer-hexo">
        <!-- 但若直接使用或修改主题，请务必保留这段声明 -->
        <small>Powered by <a target="_blank" href="https://hexo.io">Hexo</a> | Theme
            <a target="_blank" href="https://geektutu.com">Geektutu</a>
            <a href="https://github.com/geektutu/hexo-theme-geektutu" target="_blank" rel="noopener">
                <img src=""
                    alt="Github Star">
            </a>
        </small>
    </p>
    
    <p>
        <small>
            <span id="busuanzi_container_site_pv">👁<span id="busuanzi_value_site_pv"></span></span> &nbsp;
            <span id="busuanzi_container_page_pv">📚<span id="busuanzi_value_page_pv"></span></span>
        </small>
    </p>
    
    

</footer>


<script>
    window.addEventListener('load', function () {
        globalAddScript('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js');
    })
</script>

<!-- Baidu Tongji -->





    <script>
        // 非本站网址，新开tab页签
        (function () {
            var stopBubble = function (e) {
                e && e.stopPropagation && e.stopPropagation();
            }
            var links = document.querySelectorAll('a');
            [].slice.call(links).forEach(function (item) {
                if (item.href && item.href.indexOf(window.location.host) === -1) {
                    item.target = '_blank'
                }
                // 阻止冒泡，不触发彩蛋。
                item.addEventListener('click', stopBubble, false);
            });
            var article = document.querySelector('article');
            article && article.addEventListener('click', stopBubble, false)
        })();
    </script>
    
</body>

</html>